Add SwiftUI hosting smoke tests for field-level Observation - #22
Conversation
sonmbol
left a comment
There was a problem hiding this comment.
Thank you for identifying and covering this important gap. Exercising field-level dependency behavior through a real SwiftUI hosting graph adds valuable protection beyond the existing registrar-level tests. The direction is strong, and the production code remains untouched.
I found a few test-correctness and organization details that should be addressed before merge. They are noted inline below.
| timeout: 2 | ||
| ) | ||
|
|
||
| guard supportsFieldLevelObservation else { |
There was a problem hiding this comment.
Could you please use the same verified teardown path before skipping? The hierarchy has already mounted three observations, but this branch removes content without waiting for cancellation. This keeps legacy-runtime tests isolated and proves deterministic teardown on every mounted path.
guard supportsFieldLevelObservation else {
await removeContent(
from: harness,
assertingTeardownOf: model
)
throw XCTSkip(
"Field isolation requires iOS 17+ or macOS 14+."
)
}|
|
||
| model.second.emit(1) | ||
|
|
||
| await fulfillment(of: [secondRendered], timeout: 2) |
There was a problem hiding this comment.
Could you add a deterministic render-queue fence before asserting that the first subtree stayed unchanged? Waiting for the second render proves the positive event, but a mistakenly scheduled global invalidation could still be queued for the next main-loop turn, allowing a false pass. A harness method that flushes layout across a main-queue boundary would make the negative assertion reliable. A strong [self] capture is safe and preferable here because the queue releases the closure after execution and the harness must remain alive until the flush completes.
await fulfillment(of: [secondRendered], timeout: 2)
await harness.flushPendingUpdates()
XCTAssertEqual(probe.renderCount(for: .first), firstBaseline)| } | ||
| } | ||
|
|
||
| private final class HostingSignal: |
There was a problem hiding this comment.
Could you split this 575-line file by responsibility? It currently combines test cases, observation fixtures, render probes/views, and UIKit/AppKit hosting. Keeping these in SwiftUIHostingTests, HostingObservationFixture, HostingRenderViews, and AppleHostingHarness files will make future lifecycle and rendering changes much safer to review, without changing runtime behavior.
| return | ||
| } | ||
| stoppedCount += 1 | ||
| onStop?() |
There was a problem hiding this comment.
Please consume this test callback once so the signal releases the captured expectation immediately and cannot over-fulfill it if the fixture later supports restarting observation.
let action = onStop
onStop = nil
action?()
Summary
Fixes #18
Harness
Each projected field is rendered by a separate SwiftUI
Viewidentity. Theruntime path uses
KMPObservedObject, while a test-only configuration forcesthe same production store through its
ObservableObjectfallback.The source emits dependency notifications synchronously. Tests wait for
positive body-render and cancellation signals; timeouts are failure bounds
only. The field-isolation assertion is made after the B subtree has rendered,
without an inverted expectation or sleep.
The suite also includes a runtime-gated iOS 15/16 fallback test. A forced
fallback test keeps that contract covered on current runtimes when an older
simulator is unavailable.
Validation
The repository-wide
-warnings-as-errorscommand currently stops on threepre-existing Swift 6.3
WeakMutabilitydiagnostics inKMPObservableBridgeTests.swift; the new hosting test file compiles withoutdiagnostics.
Verified locally with Xcode 26.5 on macOS 26.5.2. An iOS 15/16 runtime was not
installed locally, so that runtime-specific case remains explicitly
unverified rather than being reported as passed.